// SPDX-License-Identifier: MIT
// wc_opts: command-line options parsing module

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE 1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <ctype.h>
#include <unistd.h>

#include "wc/wc_opts.h"
#include "wc/wc_title.h"
#include "wc/wc_grep.h"
#include "wc/wc_fold.h" // for future fold helpers if needed
#include "wc/wc_seclog.h"
#include "wc/wc_net.h"

// Local helpers ----------------------------------------------------------------
static size_t parse_size_with_unit_local(const char* str) {
    if (!str || !*str) return 0;
    char* end = NULL;
    unsigned long long base = strtoull(str, &end, 10);
    if (base == 0 && end == str) return 0;
    if (!end || !*end) return (size_t)base;
    unsigned long long mult = 1;
    if (end[0] == 'K' || end[0] == 'k') mult = 1024ULL;
    else if (end[0] == 'M' || end[0] == 'm') mult = 1024ULL * 1024ULL;
    else if (end[0] == 'G' || end[0] == 'g') mult = 1024ULL * 1024ULL * 1024ULL;
    else return 0; // invalid unit
    if (end[1] != '\0') return 0; // trailing junk
    unsigned long long total = base * mult;
    return (size_t)total;
}

void wc_opts_init_defaults(wc_opts_t* o) {
    if (!o) return;
    memset(o, 0, sizeof(*o));
    o->port = 43;
    o->timeout_sec = 5;
    o->retries = 2;
    o->retry_interval_ms = 300;
    o->retry_jitter_ms = 300;
    o->buffer_size = 524288; // 512K default
    o->dns_cache_size = 10;
    o->connection_cache_size = 5;
    o->cache_timeout = 300;
    o->max_hops = 5;
    o->fold_upper = 1;
    o->prefer_ipv6 = 1; // default preference ordering (IPv6 first)
    o->dns_neg_ttl = 10; // default negative DNS cache TTL (seconds)
    // DNS resolver defaults (Phase 1)
    o->dns_addrconfig = 1;
    o->dns_retry = 3;
    o->dns_retry_interval_ms = 100;
    o->dns_max_candidates = 12;
    // Fallback toggles default to enabled behavior (off means enabled)
    o->no_dns_known_fallback = 0;
    o->no_dns_force_ipv4_fallback = 0;
    o->no_iana_pivot = 0;
    o->dns_no_fallback = 0;
}

static struct option wc_long_options[] = {
    {"host", required_argument, 0, 'h'},
    {"port", required_argument, 0, 'p'},
    {"title", required_argument, 0, 'g'},
    {"grep", required_argument, 0, 1000},
    {"grep-cs", required_argument, 0, 1001},
    {"grep-line", no_argument, 0, 1002},
    {"keep-continuation-lines", no_argument, 0, 1003},
    {"grep-block", no_argument, 0, 1004},
    {"no-keep-continuation-lines", no_argument, 0, 1005},
    {"fold", no_argument, 0, 1006},
    {"fold-sep", required_argument, 0, 1007},
    {"no-fold-upper", no_argument, 0, 1008},
    {"security-log", no_argument, 0, 1009},
    {"fold-unique", no_argument, 0, 1012},
    {"buffer-size", required_argument, 0, 'b'},
    {"retries", required_argument, 0, 'r'},
    {"timeout", required_argument, 0, 't'},
    {"retry-interval-ms", required_argument, 0, 'i'},
    {"retry-jitter-ms", required_argument, 0, 'J'},
    {"retry-all-addrs", no_argument, 0, 1111},
    {"dns-cache", required_argument, 0, 'd'},
    {"conn-cache", required_argument, 0, 'c'},
    {"cache-timeout", required_argument, 0, 'T'},
    {"max-redirects", required_argument, 0, 'R'},
    {"no-redirect", no_argument, 0, 'Q'},
    {"batch", no_argument, 0, 'B'},
    {"plain", no_argument, 0, 'P'},
    {"debug", no_argument, 0, 'D'},
    {"list", no_argument, 0, 'l'},
    {"version", no_argument, 0, 'v'},
    {"help", no_argument, 0, 'H'},
    {"about", no_argument, 0, 1010},
    {"examples", no_argument, 0, 1011},
    {"selftest", no_argument, 0, 1013},
    {"debug-verbose", no_argument, 0, 1014},
    // New pacing CLI (connect-level retry pacing unified with existing env mechanism)
    {"pacing-disable", no_argument, 0, 1100},
    {"pacing-interval-ms", required_argument, 0, 1101},
    {"pacing-jitter-ms", required_argument, 0, 1102},
    {"pacing-backoff-factor", required_argument, 0, 1103},
    {"pacing-max-ms", required_argument, 0, 1104},
    {"retry-metrics", no_argument, 0, 1105},
    {"selftest-fail-first-attempt", no_argument, 0, 1106},
    {"selftest-inject-empty", no_argument, 0, 1107},
    {"selftest-grep", no_argument, 0, 1108},
    {"selftest-seclog", no_argument, 0, 1109},
    {"selftest-dns-negative", no_argument, 0, 1110},
    {"selftest-blackhole-iana", no_argument, 0, 1113},
    {"selftest-blackhole-arin", no_argument, 0, 1114},
    {"selftest-force-iana-pivot", no_argument, 0, 1115},
    // DNS / IP family preference
    {"ipv4-only", no_argument, 0, 1200},
    {"ipv6-only", no_argument, 0, 1201},
    {"prefer-ipv4", no_argument, 0, 1202},
    {"prefer-ipv6", no_argument, 0, 1203},
    {"dns-neg-ttl", required_argument, 0, 1204},
    {"no-dns-neg-cache", no_argument, 0, 1205},
    {"no-dns-addrconfig", no_argument, 0, 1206},
    {"dns-retry", required_argument, 0, 1207},
    {"dns-retry-interval-ms", required_argument, 0, 1208},
    {"dns-max-candidates", required_argument, 0, 1209},
    {"no-known-ip-fallback", no_argument, 0, 1210},
    {"no-force-ipv4-fallback", no_argument, 0, 1211},
    {"no-iana-pivot", no_argument, 0, 1212},
    {"dns-cache-stats", no_argument, 0, 1213},
    {"dns-no-fallback", no_argument, 0, 1214},
    /* language option removed */
    {0,0,0,0}
};

int wc_opts_parse(int argc, char* argv[], wc_opts_t* o) {
    if (!o) return 1;
    wc_opts_init_defaults(o);

    int opt, option_index = 0;
    int explicit_batch_flag = 0;
    // Pacing CLI accumulation
    int cli_pacing_disable_set = 0; // value presence only (flag); actual disable signalled via set flag
    int cli_pacing_interval_set = 0, cli_pacing_interval = -1;
    int cli_pacing_jitter_set = 0, cli_pacing_jitter = -1;
    int cli_pacing_backoff_set = 0, cli_pacing_backoff = -1;
    int cli_pacing_max_set = 0, cli_pacing_max = -1;
    int cli_retry_metrics = 0;
    int cli_selftest_fail_first = 0;
    int cli_selftest_inject_empty = 0;
    int cli_selftest_grep = 0;
    int cli_selftest_seclog = 0;
    int cli_selftest_dnsneg = 0;
    int cli_selftest_bh_iana = 0;
    int cli_selftest_bh_arin = 0;
    int cli_selftest_force_iana = 0;
    int cli_retry_all_addrs = 0;

    // ensure default fold separator
    if (!o->fold_sep) {
        o->fold_sep = strdup(" ");
        if (!o->fold_sep) { fprintf(stderr, "OOM initializing fold separator\n"); return 2; }
    }

    while ((opt = getopt_long(argc, argv, "h:p:g:b:r:t:i:J:d:c:T:R:QBPDlvH", wc_long_options, &option_index)) != -1) {
        switch (opt) {
            case 'h': o->host = optarg; break;
            case 'p':
                o->port = atoi(optarg);
                if (o->port <=0 || o->port>65535) { fprintf(stderr,"Error: Invalid port\n"); return 3; }
                break;
            case 'g':
                wc_title_free();
                wc_title_set_enabled(1);
                if (wc_title_parse_patterns(optarg) < 0) { wc_title_free(); return 4; }
                o->title_pat = optarg;
                break;
            case 1000: // --grep (ci)
                if (wc_grep_compile(optarg,0) < 0) return 5;
                o->grep_pat = optarg; o->grep_case_sensitive = 0; break;
            case 1001: // --grep-cs
                if (wc_grep_compile(optarg,1) < 0) return 6;
                o->grep_pat = optarg; o->grep_case_sensitive = 1; break;
            case 1002: wc_grep_set_line_mode(1); o->grep_mode_block = 0; break;
            case 1003: wc_grep_set_keep_continuation(1); o->keep_continuation = 1; break;
            case 1004: wc_grep_set_line_mode(0); o->grep_mode_block = 1; break;
            case 1005: wc_grep_set_keep_continuation(0); o->keep_continuation = 0; break;
            case 1006: o->fold = 1; break;
            case 1007: {
                if (o->fold_sep) { free((char*)o->fold_sep); o->fold_sep=NULL; }
                if (optarg && strcmp(optarg, "\\t") == 0) o->fold_sep = strdup("\t");
                else if (optarg && strcmp(optarg, "\\n") == 0) o->fold_sep = strdup("\n");
                else if (optarg && strcmp(optarg, "\\r") == 0) o->fold_sep = strdup("\r");
                else if (optarg && (strcmp(optarg, "\\s") == 0 || strcmp(optarg, "space") == 0)) o->fold_sep = strdup(" ");
                else o->fold_sep = strdup(optarg ? optarg : " ");
                if (!o->fold_sep) { fprintf(stderr,"Error: OOM parsing --fold-sep\n"); return 7; }
            } break;
            case 1008: o->fold_upper = 0; break;
            case 1009: o->security_log = 1; break;
            case 1012: o->fold_unique = 1; break;
            case 'B': explicit_batch_flag = 1; break;
            case 'Q': o->no_redirect = 1; break;
            case 'R': o->max_hops = atoi(optarg); if (o->max_hops<0){ fprintf(stderr,"Error: Invalid max redirects\n"); return 8;} break;
            case 'P': o->plain_mode = 1; break;
            case 'D': o->debug = 1; break;
            case 'l': o->show_servers = 1; break;
            case 'v': o->show_version = 1; break;
            case 'H': o->show_help = 1; break;
            case 1010: o->show_about = 1; break;
            case 1011: o->show_examples = 1; break;
            case 1013: o->show_selftest = 1; break;
            case 1014: o->debug_verbose = 1; break;
            case 1100: // --pacing-disable
                cli_pacing_disable_set = 1; break;
            case 1101: { // --pacing-interval-ms
                long v = strtol(optarg, NULL, 10);
                if (v <= 0 || v > 60000) { fprintf(stderr, "Error: Invalid --pacing-interval-ms\n"); return 18; }
                cli_pacing_interval_set = 1; cli_pacing_interval = (int)v;
            } break;
            case 1102: { // --pacing-jitter-ms
                long v = strtol(optarg, NULL, 10);
                if (v < 0 || v > 60000) { fprintf(stderr, "Error: Invalid --pacing-jitter-ms\n"); return 19; }
                cli_pacing_jitter_set = 1; cli_pacing_jitter = (int)v;
            } break;
            case 1103: { // --pacing-backoff-factor
                long v = strtol(optarg, NULL, 10);
                if (v < 1 || v > 16) { fprintf(stderr, "Error: Invalid --pacing-backoff-factor (1..16)\n"); return 20; }
                cli_pacing_backoff_set = 1; cli_pacing_backoff = (int)v;
            } break;
            case 1104: { // --pacing-max-ms
                long v = strtol(optarg, NULL, 10);
                if (v < 1 || v > 60000) { fprintf(stderr, "Error: Invalid --pacing-max-ms\n"); return 21; }
                cli_pacing_max_set = 1; cli_pacing_max = (int)v;
            } break;
            case 1105: cli_retry_metrics = 1; break;
            case 1106: cli_selftest_fail_first = 1; break;
            case 1107: cli_selftest_inject_empty = 1; break;
            case 1108: cli_selftest_grep = 1; break;
            case 1109: cli_selftest_seclog = 1; break;
            case 1110: cli_selftest_dnsneg = 1; break;
            case 1113: cli_selftest_bh_iana = 1; break;
            case 1114: cli_selftest_bh_arin = 1; break;
            case 1115: cli_selftest_force_iana = 1; break;
            case 1111: cli_retry_all_addrs = 1; break;
            case 1200: o->ipv4_only = 1; o->ipv6_only=o->prefer_ipv4=o->prefer_ipv6=0; break;
            case 1201: o->ipv6_only = 1; o->ipv4_only=o->prefer_ipv4=o->prefer_ipv6=0; break;
            case 1202: o->prefer_ipv4 = 1; o->prefer_ipv6=o->ipv4_only=o->ipv6_only=0; break;
            case 1203: o->prefer_ipv6 = 1; o->prefer_ipv4=o->ipv4_only=o->ipv6_only=0; break;
            case 1204: {
                long v = strtol(optarg, NULL, 10);
                if (v < 1 || v > 3600) { fprintf(stderr, "Error: Invalid --dns-neg-ttl (1..3600)\n"); return 22; }
                o->dns_neg_ttl = (int)v;
            } break;
            case 1205: o->dns_neg_cache_disable = 1; break;
            case 1206: o->dns_addrconfig = 0; break;
            case 1207: {
                long v = strtol(optarg, NULL, 10);
                if (v < 1 || v > 10) { fprintf(stderr, "Error: Invalid --dns-retry (1..10)\n"); return 23; }
                o->dns_retry = (int)v;
            } break;
            case 1208: {
                long v = strtol(optarg, NULL, 10);
                if (v < 0 || v > 5000) { fprintf(stderr, "Error: Invalid --dns-retry-interval-ms (0..5000)\n"); return 24; }
                o->dns_retry_interval_ms = (int)v;
            } break;
            case 1209: {
                long v = strtol(optarg, NULL, 10);
                if (v < 1 || v > 64) { fprintf(stderr, "Error: Invalid --dns-max-candidates (1..64)\n"); return 25; }
                o->dns_max_candidates = (int)v;
            } break;
            case 1210: o->no_dns_known_fallback = 1; break;
            case 1211: o->no_dns_force_ipv4_fallback = 1; break;
            case 1212: o->no_iana_pivot = 1; break;
            case 1213: o->dns_cache_stats = 1; break;
            case 1214: o->dns_no_fallback = 1; break;
            /* language option removed */
            case 'b': {
                size_t new_size = parse_size_with_unit_local(optarg);
                if (new_size == 0) { fprintf(stderr,"Error: Invalid buffer size '%s'\n", optarg); return 9; }
                if (new_size > 1024ULL*1024ULL*1024ULL) new_size = 1024ULL*1024ULL*1024ULL;
                if (new_size < 1024) new_size = 1024;
                o->buffer_size = new_size;
            } break;
            case 'r': o->retries = atoi(optarg); if (o->retries < 0){ fprintf(stderr,"Error: Invalid retry count\n"); return 10;} break;
            case 't': o->timeout_sec = atoi(optarg); if (o->timeout_sec <=0){ fprintf(stderr,"Error: Invalid timeout\n"); return 11;} break;
            case 'i': o->retry_interval_ms = atoi(optarg); if (o->retry_interval_ms <0){ fprintf(stderr,"Error: Invalid retry interval\n"); return 12;} break;
            case 'J': o->retry_jitter_ms = atoi(optarg); if (o->retry_jitter_ms <0){ fprintf(stderr,"Error: Invalid retry jitter\n"); return 13;} break;
            case 'd': o->dns_cache_size = atoi(optarg); if (o->dns_cache_size <=0){ fprintf(stderr,"Error: Invalid DNS cache size\n"); return 14;} if (o->dns_cache_size>20) o->dns_cache_size=20; break;
            case 'c': o->connection_cache_size = atoi(optarg); if (o->connection_cache_size <=0){ fprintf(stderr,"Error: Invalid connection cache size\n"); return 15;} if (o->connection_cache_size>10) o->connection_cache_size=10; break;
            case 'T': o->cache_timeout = atoi(optarg); if (o->cache_timeout <=0){ fprintf(stderr,"Error: Invalid cache timeout\n"); return 16;} break;
            default:
                // Unknown option handled by getopt_long already -> show help upstream
                return 17;
        }
    }

    // Auto batch mode if stdin is not a TTY and -B not explicitly supplied
    if (explicit_batch_flag || !isatty(fileno(stdin))) {
        o->batch_mode = 1;
    }
    o->explicit_batch = explicit_batch_flag;

    // Surface batch mode to other modules via environment for lightweight coordination
    // Apply pacing & metrics config to net stack (no environment usage)
    wc_net_set_retry_metrics_enabled(cli_retry_metrics);
    wc_net_set_selftest_fail_first(cli_selftest_fail_first);
    wc_net_set_pacing_config(
        cli_pacing_disable_set ? 1 : -1,
        cli_pacing_interval_set ? cli_pacing_interval : -1,
        cli_pacing_jitter_set ? cli_pacing_jitter : -1,
        cli_pacing_backoff_set ? cli_pacing_backoff : -1,
        cli_pacing_max_set ? cli_pacing_max : -1
    );
    wc_net_set_retry_scope_all_addrs(cli_retry_all_addrs);

    // Propagate selftest toggles across modules
    extern void wc_selftest_set_inject_empty(int enabled);
    extern void wc_selftest_set_grep_test(int enabled);
    extern void wc_selftest_set_seclog_test(int enabled);
    extern void wc_selftest_set_dns_negative(int enabled);
    extern void wc_selftest_set_blackhole_iana(int enabled);
    extern void wc_selftest_set_blackhole_arin(int enabled);
    extern void wc_selftest_set_force_iana_pivot(int enabled);
    wc_selftest_set_inject_empty(cli_selftest_inject_empty);
    wc_selftest_set_grep_test(cli_selftest_grep);
    wc_selftest_set_seclog_test(cli_selftest_seclog);
    wc_selftest_set_dns_negative(cli_selftest_dnsneg);
    wc_selftest_set_blackhole_iana(cli_selftest_bh_iana);
    wc_selftest_set_blackhole_arin(cli_selftest_bh_arin);
    wc_selftest_set_force_iana_pivot(cli_selftest_force_iana);

    // Apply security log module enable now
    wc_seclog_set_enabled(o->security_log);
    return 0;
}

void wc_opts_free(wc_opts_t* o) {
    if (!o) return;
    if (o->fold_sep) { free((char*)o->fold_sep); o->fold_sep = NULL; }
}
